"""
WeCom (企业微信) webhook file sender

Uploads a file to the webhook's upload endpoint to obtain a media_id,
then sends a file message referencing that media_id.

Usage (CLI):
  python wecom_file_sender.py --key YOUR_WEBHOOK_KEY path/to/file

Programmatic usage:
  from wecom_file_sender import WeComFileSender
  sender = WeComFileSender(key="YOUR_WEBHOOK_KEY")
  sender.send_file("README.md")

Notes:
- Only file messages are sent (msgtype=file); WeCom accepts common types
  like .md, .docx, .pdf, .png, etc.
- media_id is valid for ~3 days; this tool uploads each time before sending.
"""

from __future__ import annotations

import argparse
import json
import mimetypes
from pathlib import Path
from typing import Optional
from urllib.parse import urlparse, parse_qs

try:
    import requests  # type: ignore
except Exception as e:  # pragma: no cover
    raise SystemExit(
        "The 'requests' package is required. Install with: pip install requests"
    ) from e


def _guess_content_type(path: Path) -> str:
    suffix = path.suffix.lower()
    if suffix in {".md", ".markdown", ".mdown", ".mkdn"}:
        return "text/markdown"
    guessed, _ = mimetypes.guess_type(str(path))
    return guessed or "application/octet-stream"


def _extract_key_from_webhook(webhook: str) -> Optional[str]:
    try:
        parsed = urlparse(webhook)
        if not parsed.query:
            return None
        qs = parse_qs(parsed.query)
        vals = qs.get("key")
        return vals[0] if vals else None
    except Exception:
        return None


class WeComFileSender:
    """WeCom webhook file sender for markdown files.

    Initialize with either a webhook key or full webhook URL.
    """

    BASE = "https://qyapi.weixin.qq.com/cgi-bin/webhook"

    def __init__(self, key: Optional[str] = None, webhook: Optional[str] = None, timeout: int = 15, disable_proxy: bool = False):
        if not key and not webhook:
            raise ValueError("Either 'key' or 'webhook' must be provided")
        if not key and webhook:
            key = _extract_key_from_webhook(webhook) or webhook
        if key and key.startswith("http") and not webhook:
            # user passed full webhook as 'key'
            webhook = key
            key = _extract_key_from_webhook(webhook) or None
        if not key:
            raise ValueError("Unable to determine webhook key; please pass a valid key or full webhook URL")

        self.key = key
        self.timeout = timeout
        self.upload_url = f"{self.BASE}/upload_media?key={self.key}&type=file"
        self.send_url = f"{self.BASE}/send?key={self.key}"
        # Prepare a session; optionally ignore proxy/ENV settings to avoid corporate proxy issues
        self.session = requests.Session()
        if disable_proxy:
            self.session.trust_env = False

    def upload_file(self, file_path: str, display_name: Optional[str] = None) -> str:
        path = Path(file_path)
        if not path.exists() or not path.is_file():
            raise FileNotFoundError(f"File not found: {file_path}")
        if path.stat().st_size <= 0:
            raise ValueError("File is empty; upload requires non-empty file")

        content_type = _guess_content_type(path)
        filename = display_name or path.name

        with path.open("rb") as fh:
            files = {
                # tuple: (filename, fileobj, content_type)
                "media": (filename, fh, content_type),
            }
            try:
                resp = self.session.post(self.upload_url, files=files, timeout=self.timeout)
            except requests.RequestException as e:  # pragma: no cover
                raise RuntimeError(f"Upload request failed: {e}") from e

        try:
            data = resp.json()
        except Exception as e:  # pragma: no cover
            raise RuntimeError(f"Invalid JSON response from upload endpoint: {resp.text}") from e

        if data.get("errcode") != 0:
            raise RuntimeError(f"Upload failed: errcode={data.get('errcode')} errmsg={data.get('errmsg')}")

        media_id = data.get("media_id")
        if not media_id:
            raise RuntimeError("Upload succeeded but no media_id returned")
        return media_id

    def send_file_by_media_id(self, media_id: str) -> dict:
        payload = {
            "msgtype": "file",
            "file": {"media_id": media_id},
        }
        try:
            resp = self.session.post(self.send_url, json=payload, timeout=self.timeout)
        except requests.RequestException as e:  # pragma: no cover
            raise RuntimeError(f"Send request failed: {e}") from e

        try:
            data = resp.json()
        except Exception as e:  # pragma: no cover
            raise RuntimeError(f"Invalid JSON response from send endpoint: {resp.text}") from e

        if data.get("errcode") != 0:
            raise RuntimeError(f"Send failed: errcode={data.get('errcode')} errmsg={data.get('errmsg')}")
        return data

    def send_markdown_file(self, file_path: str, display_name: Optional[str] = None) -> dict:
        # Backward-compatible API for markdown; delegates to generic sender
        media_id = self.upload_file(file_path, display_name=display_name)
        return self.send_file_by_media_id(media_id)

    def send_file(self, file_path: str, display_name: Optional[str] = None) -> dict:
        media_id = self.upload_file(file_path, display_name=display_name)
        return self.send_file_by_media_id(media_id)


def _build_cli() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(description="Send a file via WeCom webhook (two API calls)")
    p.add_argument("file", help="Path to a file to send (e.g. .md, .docx)")
    p.add_argument("--key", "-k", help="WeCom webhook key (preferred)")
    p.add_argument("--webhook", help="Full WeCom webhook URL (alternative to --key)")
    p.add_argument("--name", help="Display filename to show in WeCom (optional)")
    p.add_argument("--timeout", type=int, default=15, help="HTTP timeout seconds (default: 15)")
    p.add_argument("--no-proxy", action="store_true", help="Ignore environment proxies (set trust_env=False)")
    p.add_argument("--json", action="store_true", help="Print raw JSON responses")
    return p


def main() -> None:
    args = _build_cli().parse_args()
    sender = WeComFileSender(key=args.key, webhook=args.webhook, timeout=args.timeout, disable_proxy=args.no_proxy)
    result = sender.send_file(args.file, display_name=args.name)
    if args.json:
        print(json.dumps(result, ensure_ascii=False))
    else:
        print("Send OK:", result.get("errmsg", "ok"))


if __name__ == "__main__":  # pragma: no cover
    main()
